// 杂七杂八的一些帮助方法
import _ from 'lodash'
import { MiscDef } from './misc.d'
import Decimal from 'decimal.js'

// 清除对象中的null值
const removePropertyOfNull = function (obj: Record<string, any>) {
  Object.keys(obj).forEach((item) => {
    if (obj[item] !== 0 && (typeof obj[item] !== 'boolean') && !obj[item]) {
      delete obj[item]
    }
  })
  return obj
}

// 准备简单表单对象
// 我们的表单对象(通常命名为form，或lineInfos.xxxLine.lineForm)里面通常包含了要提交的数据，但有些数据是不用提交的
// 比如stateLogList这些服务器传过来的数据，仅供客户端显示，不用传回服务器去，还有些固定字段，如createDt，createP，updateDt，updateP等
// 也是不需要传回服务器端的，也许还有一些特别的字段，不需要传回服务器，需要手工指定哪些字段，再就是一些空字段，不需要传回服务器，传回去反倒有问题
// 如不可空的DateTime类型，如果尝试传给服务器端null，则会出现绑定错误，基于这样的需求，写一个统一的方法，用于准备form数据
// 注意：如果要提交的form数据有复杂结构，可能不适合使用此方法(这种情况很少见！)
// 参数说明：
//   obj: 原始form对象
//   omitFields: 要忽略的字段，用数组形式传入，如['stateLog', 'asnState']等，注:
//     有些字段虽然服务器不需要，但提交上去也不会有什么影响，对于这种字段可以填写也可以不填，除了直接指定的字段之外，这些字段也会被默认忽略:
//     createDt，createP，createPn, updateDt，updateP, updatePn
//   extra: 额外要新增的内容，以对象形式传入，如: { pageNo: 1, pageSize: 0x7fffffff }
const makeSimpleFormObj = function (formObj: Record<string, any>, omitFields?: string[], extra?: Record<string, any>) {
  if (!omitFields) {
    omitFields = []
  }
  omitFields = _.union(omitFields, ['createDt', 'createP', 'createPn', 'updateDt', 'updateP', 'updatePn'])
  const newFormObj = _.omit(formObj, omitFields)
  if (extra) {
    _.assign(newFormObj, extra)
  }
  return removePropertyOfNull(newFormObj)
}

// 准备简单GET对象，与makeSimpleFormObj的差别仅在于不会默认移除'createDt', 'createP', 'createPn', 'updateDt', 'updateP', 'updatePn'这些字段
const makeSimpleQueryObj = function (formObj: Record<string, any>, omitFields?: string[], extra?: Record<string, any>) {
  if (!Array.isArray(omitFields)) {
    omitFields = []
  }
  const newFormObj = _.omit(formObj, omitFields)
  if (extra) {
    _.assign(newFormObj, extra)
  }
  return removePropertyOfNull(newFormObj)
}

// 把一个对象中所有的的成员字符串做一个trim动作，非字符串不受影响，不支持嵌套，直接改变原先对象的属性
const trimAll = function (obj: Record<string, any>) {
  _.forIn(obj, function (value, key) {
    if (typeof value === 'string') {
      obj[key] = value.trim()
    }
  })
}

// 将对象所有属性置null, 可指定忽略哪些属性，如 ['a', 'b']，本方法不支持嵌套
const setPropertiesToNull = function (obj: Record<string, any>, ignore?: string[]) {
  _.forIn(obj, function (value, key) {
    if (!ignore || _.indexOf(ignore, key) === -1) {
      obj[key] = null
    }
  })
}

// 对象简单赋值(必须target对象有这个元素，才会有source对象中的同名元素赋值覆盖)
const simpleAssign = function (target: Record<string, any>, source: Record<string, any>) {
  _.assign(target, _.pick(source, _.keys(target)))
}

// 根据给出的key的顺序对数据进行排序，如：
// sortByKeys([{key:'a'}, {key:'b'}, {key:'c'}], "key", ['c', 'a', 'b'])
// 结果为 [{key:'c'}, {key:'a'}, {key:'b'}]
const sortByKeys = function (arr: Record<string, any>[], keyName: string, keys: string[]) {
  return _.sortBy(arr, function (item) {
    return _.indexOf(keys, item[keyName])
  })
}

// 判断是否字符串并且非空(Not empty / Not whitespace)
const isStringWithContent = function (str: string) {
  return str.trim().length > 0
}

// 根据baseUrl及param生成新的URL
const makeUrlWithQueryParam = function (baseUrl: string, param?: Record<string, string | number | boolean>) {
  if (param) {
    const parts: string[] = []
    _.forIn(param, function (value, key) {
      parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value))
    })
    const queryString = parts.join('&')
    if (baseUrl.indexOf('?') === -1) {
      baseUrl += '?'
    } else {
      baseUrl += '&'
    }
    baseUrl += queryString
  }
  return baseUrl
}

// 从紧凑型字符串中提取源字符串，紧凑型字符串用"|"分隔，"\p"标识"|", "\b"标识"\"，会返回空集，但不会返回null
const extractCompactString = function (compactString: string) {
  if (!compactString) {
    return []
  }
  const arr = compactString.split('|')
  return _.map(arr, function (value) {
    return value.replace('\\p', '|').replace('\\b', '\\')
  })
}

// 从紧凑型字符串中提取源字符串，带标识头，如果字符串不以标识头开始，则返回空集，不会返回null
const extractCompactStringWithPrefix = function (compactString: string, prefix = 'n2D:') {
  if (!compactString || !compactString.startsWith(prefix)) {
    return []
  }
  return extractCompactString(compactString.substring(prefix.length))
}

// 尝试从紧凑型字符串中，根据段index，提取原字符，如果prefix不符合，或提取不到，则原封不动返回参数字符串
const tryExtractCompactStringWithIndex = function (compactString: string, index: number, prefix = 'n2D:') {
  const arr = extractCompactStringWithPrefix(compactString, prefix)
  if (index < arr.length) {
    return arr[index]
  } else {
    return compactString
  }
}

// 尝试从一个类型未知的对象中获取属性(必须是字符串)，获取不到或者不是字符串返回null
const getStrProp = function (obj: any, propName: string): string | null {
  const a = (<Record<string, any>>obj)[propName]
  if (typeof a === 'string') {
    return a
  }
  return null
}

// 分摊算法
// total:总值
// scale:比例
// decimalNum小数位数(0-6)，默认-1表示不限
const numberScaleDivide = function (total: number, scale: number[], decimalNum: number) {
  return new Promise<number[]>((resolve, reject) => {
    if (decimalNum != -1 && (Number( roundTo(new Decimal(total), decimalNum)) != Number(new Decimal(total)))) {
      reject('由于小数点后的数字超过了指定的值，所以不能进行平摊')
    }
    const partNum = scale.length
    const result = new Array(partNum)
    if (partNum === 0) {
      resolve([])
    }
    let scaleSum = new Decimal(0)
    _.forEach(scale, (v) => {
      scaleSum = Decimal.sum(scaleSum, v)
    })
    if (scaleSum === new Decimal(0)) {
      reject('分摊比例总和不能为0')
    }
    if (partNum === 1) {
      resolve([total])
    }
    if (decimalNum > 6)
      decimalNum = 6
    if (decimalNum < -1)
      decimalNum = -1
    for (let i=0; i<partNum; i++) {
      result[i] = new Decimal(total).mul(new Decimal(scale[i])).div(new Decimal(scaleSum))
      if(decimalNum!=-1) {
        result[i] = roundTo(new Decimal(result[i]), decimalNum)
      }
    }
    let totalTest = new Decimal(0)
    _.forEach(result, (v) => {
      totalTest = Decimal.sum(totalTest, v)
    })
    const remainder = new Decimal(total).sub(new Decimal(totalTest))
      //摊到最大的上面
      let maxIdx = 0;
      for(let i=1; i<result.length; i++) {
        if(result[i] > result[maxIdx]) {
          maxIdx = i
        }
      }
      result[maxIdx] = Decimal.sum(result[maxIdx], remainder)
    resolve(result)
  })
}

const roundTo = function (decimalVal:Decimal, decimalNum:number) {
  const e = Decimal.pow(10, decimalNum)
  return decimalVal.mul(e).round().div(e)
}

export default {
  makeSimpleFormObj,
  makeSimpleQueryObj,
  trimAll,
  removePropertyOfNull,
  setPropertiesToNull,
  simpleAssign,
  sortByKeys,
  isStringWithContent,
  makeUrlWithQueryParam,
  extractCompactString,
  extractCompactStringWithPrefix,
  tryExtractCompactStringWithIndex,
  getStrProp: getStrProp,
  numberScaleDivide,
  roundTo
} as MiscDef
